﻿using System;
using System.Text;
using System.Drawing;
using System.Text.RegularExpressions;
using System.Collections.Generic;

namespace FastColoredTextBoxNS
{
  /// <summary>
  /// Diapason of text chars
  /// </summary>
  public class Range : IEnumerable<Place>
  {
    Place start;
    Place end;
    public readonly FastColoredTextBox tb;
    int preferedPos = -1;
    int updating = 0;

    string cachedText;
    List<Place> cachedCharIndexToPlace;
    int cachedTextVersion = -1;

    /// <summary>
    /// Constructor
    /// </summary>
    public Range( FastColoredTextBox tb )
    {
      this.tb = tb;
    }

    /// <summary>
    /// Return true if no selected text
    /// </summary>
    public virtual bool IsEmpty
    {
      get
      {
        if ( ColumnSelectionMode )
          return Start.iChar == End.iChar;
        return Start == End;
      }
    }

    private bool columnSelectionMode;

    /// <summary>
    /// Column selection mode
    /// </summary>
    public bool ColumnSelectionMode
    {
      get
      {
        return columnSelectionMode;
      }
      set
      {
        columnSelectionMode = value;
      }
    }

    /// <summary>
    /// Constructor
    /// </summary>
    public Range( FastColoredTextBox tb, int iStartChar, int iStartLine, int iEndChar, int iEndLine )
      : this( tb )
    {
      start = new Place( iStartChar, iStartLine );
      end = new Place( iEndChar, iEndLine );
    }

    /// <summary>
    /// Constructor
    /// </summary>
    public Range( FastColoredTextBox tb, Place start, Place end )
      : this( tb )
    {
      this.start = start;
      this.end = end;
    }

    /// <summary>
    /// Constructor. Creates range of the line
    /// </summary>
    public Range( FastColoredTextBox tb, int iLine )
      : this( tb )
    {
      start = new Place( 0, iLine );
      end = new Place( tb[iLine].Count, iLine );
    }

    public bool Contains( Place place )
    {
      if ( place.iLine < Math.Min( start.iLine, end.iLine ) )
        return false;
      if ( place.iLine > Math.Max( start.iLine, end.iLine ) )
        return false;

      Place s = start;
      Place e = end;
      //normalize start and end
      if ( s.iLine > e.iLine || ( s.iLine == e.iLine && s.iChar > e.iChar ) )
      {
        var temp = s;
        s = e;
        e = temp;
      }

      if ( columnSelectionMode )
      {
        if ( place.iChar < s.iChar || place.iChar > e.iChar )
          return false;
      }
      else
      {
        if ( place.iLine == s.iLine && place.iChar < s.iChar )
          return false;
        if ( place.iLine == e.iLine && place.iChar > e.iChar )
          return false;
      }

      return true;
    }

    /// <summary>
    /// Returns intersection with other range,
    /// empty range returned otherwise
    /// </summary>
    /// <param name="range"></param>
    /// <returns></returns>
    public virtual Range GetIntersectionWith( Range range )
    {
      if ( ColumnSelectionMode )
        return GetIntersectionWith_ColumnSelectionMode( range );

      Range r1 = this.Clone();
      Range r2 = range.Clone();
      r1.Normalize();
      r2.Normalize();
      Place newStart = r1.Start > r2.Start ? r1.Start : r2.Start;
      Place newEnd = r1.End < r2.End ? r1.End : r2.End;
      if ( newEnd < newStart )
        return new Range( tb, start, start );
      return tb.GetRange( newStart, newEnd );
    }

    /// <summary>
    /// Returns union with other range.
    /// </summary>
    /// <param name="range"></param>
    /// <returns></returns>
    public Range GetUnionWith( Range range )
    {
      Range r1 = this.Clone();
      Range r2 = range.Clone();
      r1.Normalize();
      r2.Normalize();
      Place newStart = r1.Start < r2.Start ? r1.Start : r2.Start;
      Place newEnd = r1.End > r2.End ? r1.End : r2.End;

      return tb.GetRange( newStart, newEnd );
    }

    /// <summary>
    /// Select all chars of control
    /// </summary>
    public void SelectAll()
    {
      ColumnSelectionMode = false;

      Start = new Place( 0, 0 );
      if ( tb.LinesCount == 0 )
        Start = new Place( 0, 0 );
      else
      {
        end = new Place( 0, 0 );
        start = new Place( tb[tb.LinesCount - 1].Count, tb.LinesCount - 1 );
      }
      if ( this == tb.Selection )
        tb.Invalidate();
    }

    /// <summary>
    /// Start line and char position
    /// </summary>
    public Place Start
    {
      get
      {
        return start;
      }
      set
      {
        end = start = value;
        preferedPos = -1;
        OnSelectionChanged();
      }
    }

    /// <summary>
    /// Finish line and char position
    /// </summary>
    public Place End
    {
      get
      {
        return end;
      }
      set
      {
        end = value;
        OnSelectionChanged();
      }
    }

    /// <summary>
    /// Text of range
    /// </summary>
    /// <remarks>This property has not 'set' accessor because undo/redo stack works only with 
    /// FastColoredTextBox.Selection range. So, if you want to set text, you need to use FastColoredTextBox.Selection
    /// and FastColoredTextBox.InsertText() mehtod.
    /// </remarks>
    public virtual string Text
    {
      get
      {
        if ( ColumnSelectionMode )
          return Text_ColumnSelectionMode;

        int fromLine = Math.Min( end.iLine, start.iLine );
        int toLine = Math.Max( end.iLine, start.iLine );
        int fromChar = FromX;
        int toChar = ToX;
        if ( fromLine < 0 )
          return null;
        //
        StringBuilder sb = new StringBuilder();
        for ( int y = fromLine; y <= toLine; y++ )
        {
          int fromX = y == fromLine ? fromChar : 0;
          int toX = y == toLine ? Math.Min( tb[y].Count - 1, toChar - 1 ) : tb[y].Count - 1;
          for ( int x = fromX; x <= toX; x++ )
          {
            var  singleChar = tb[y][x].c;
            if ( ( tb.AllowTabs )
            &&   ( singleChar == '\t' ) )
            {
              sb.Append( '\t' );

              bool    firstChar = true;
              while ( ( firstChar )
              || ( ( x % tb.TabLength ) > 0 ) )
              {
                firstChar = false;
                ++x;
              }
              --x;
            }
            else
            {
              sb.Append( tb[y][x].c );
            }
          }
          if ( y != toLine && fromLine != toLine )
            sb.AppendLine();
        }
        return sb.ToString();
      }
    }

    internal void GetText( out string text, out List<Place> charIndexToPlace )
    {
      //try get cached text
      if ( tb.TextVersion == cachedTextVersion )
      {
        text = cachedText;
        charIndexToPlace = cachedCharIndexToPlace;
        return;
      }
      //
      int fromLine = Math.Min( end.iLine, start.iLine );
      int toLine = Math.Max( end.iLine, start.iLine );
      int fromChar = FromX;
      int toChar = ToX;

      StringBuilder sb = new StringBuilder( ( toLine - fromLine ) * 50 );
      charIndexToPlace = new List<Place>( sb.Capacity );
      if ( fromLine >= 0 )
      {
        for ( int y = fromLine; y <= toLine; y++ )
        {
          int fromX = y == fromLine ? fromChar : 0;
          int toX = y == toLine ? Math.Min( toChar - 1, tb[y].Count - 1 ) : tb[y].Count - 1;
          for ( int x = fromX; x <= toX; x++ )
          {
            sb.Append( tb[y][x].c );
            charIndexToPlace.Add( new Place( x, y ) );
          }
          if ( y != toLine && fromLine != toLine )
            foreach ( char c in Environment.NewLine )
            {
              sb.Append( c );
              charIndexToPlace.Add( new Place( tb[y].Count/*???*/, y ) );
            }
        }
      }
      text = sb.ToString();
      charIndexToPlace.Add( End > Start ? End : Start );
      //caching
      cachedText = text;
      cachedCharIndexToPlace = charIndexToPlace;
      cachedTextVersion = tb.TextVersion;
    }

    /// <summary>
    /// Returns first char after Start place
    /// </summary>
    public char CharAfterStart
    {
      get
      {
        if ( Start.iChar >= tb[Start.iLine].Count )
          return '\n';
        return tb[Start.iLine][Start.iChar].c;
      }
    }

    /// <summary>
    /// Returns first char before Start place
    /// </summary>
    public char CharBeforeStart
    {
      get
      {
        if ( Start.iChar > tb[Start.iLine].Count )
          return '\n';
        if ( Start.iChar <= 0 )
          return '\n';
        return tb[Start.iLine][Start.iChar - 1].c;
      }
    }

    /// <summary>
    /// Returns required char's number before start of the Range
    /// </summary>
    public string GetCharsBeforeStart( int charsCount )
    {
      var pos = tb.PlaceToPosition( Start ) - charsCount;
      if ( pos < 0 )
        pos = 0;

      return new Range( tb, tb.PositionToPlace( pos ), Start ).Text;
    }

    /// <summary>
    /// Returns required char's number after start of the Range
    /// </summary>
    public string GetCharsAfterStart( int charsCount )
    {
      return GetCharsBeforeStart( -charsCount );
    }

    /// <summary>
    /// Clone range
    /// </summary>
    /// <returns></returns>
    public Range Clone()
    {
      return (Range)MemberwiseClone();
    }

    /// <summary>
    /// Return minimum of end.X and start.X
    /// </summary>
    internal int FromX
    {
      get
      {
        if ( end.iLine < start.iLine )
          return end.iChar;
        if ( end.iLine > start.iLine )
          return start.iChar;
        return Math.Min( end.iChar, start.iChar );
      }
    }

    /// <summary>
    /// Return maximum of end.X and start.X
    /// </summary>
    internal int ToX
    {
      get
      {
        if ( end.iLine < start.iLine )
          return start.iChar;
        if ( end.iLine > start.iLine )
          return end.iChar;
        return Math.Max( end.iChar, start.iChar );
      }
    }

    public int FromLine
    {
      get
      {
        return Math.Min( Start.iLine, End.iLine );
      }
    }

    public int ToLine
    {
      get
      {
        return Math.Max( Start.iLine, End.iLine );
      }
    }

    /// <summary>
    /// Move range right
    /// </summary>
    /// <remarks>This method jump over folded blocks</remarks>
    public bool GoRight()
    {
      Place prevStart = start;
      GoRight( false );
      return prevStart != start;
    }

    /// <summary>
    /// Move range left
    /// </summary>
    /// <remarks>This method can to go inside folded blocks</remarks>
    public virtual bool GoRightThroughFolded()
    {
      if ( ColumnSelectionMode )
        return GoRightThroughFolded_ColumnSelectionMode();

      if ( start.iLine >= tb.LinesCount - 1 && start.iChar >= tb[tb.LinesCount - 1].Count )
        return false;

      if ( start.iChar < tb[start.iLine].Count )
      {
        if ( ( tb.AllowTabs )
                  && ( tb[start.iLine][start.iChar].c == '\t' ) )
        {
          int delta = tb.TabLength - ( start.iChar % tb.TabLength );
          if ( delta == 0 )
          {
            delta = tb.TabLength;
          }
          start.Offset( delta, 0 );
        }
        else
        {
          start.Offset( 1, 0 );
        }
      }
      else
        start = new Place( 0, start.iLine + 1 );

      preferedPos = -1;
      end = start;
      OnSelectionChanged();
      return true;
    }

    /// <summary>
    /// Move range left
    /// </summary>
    /// <remarks>This method jump over folded blocks</remarks>
    public bool GoLeft()
    {
      ColumnSelectionMode = false;

      Place prevStart = start;
      GoLeft( false );
      return prevStart != start;
    }

    /// <summary>
    /// Move range left
    /// </summary>
    /// <remarks>This method can to go inside folded blocks</remarks>
    public bool GoLeftThroughFolded()
    {
      ColumnSelectionMode = false;

      if ( start.iChar == 0 && start.iLine == 0 )
        return false;

      if ( start.iChar > 0 )
      {
        if ( ( tb.AllowTabs )
        &&   ( start.iChar > 0 )
        &&   ( start.iChar - 1 < tb[start.iLine].Count )
        &&   ( tb[start.iLine][start.iChar - 1].c == '\t' ) )
        {
          int delta = start.iChar % tb.TabLength;
          if ( delta == 0 )
          {
            delta = tb.TabLength;
          }
          for ( int i = 0; i < delta; ++i )
          {
            if ( tb[start.iLine][start.iChar - 1].c == '\t' )
            {
              start.Offset( -1, 0 );
            }
            else
            {
              break;
            }
          }
        }
        else
        {
          start.Offset( -1, 0 );
        }
      }
      else
        start = new Place( tb[start.iLine - 1].Count, start.iLine - 1 );

      preferedPos = -1;
      end = start;
      OnSelectionChanged();
      return true;
    }

    public void GoLeft( bool shift )
    {
      ColumnSelectionMode = false;

      if ( !shift )
        if ( start > end )
        {
          Start = End;
          return;
        }

      if ( start.iChar != 0 || start.iLine != 0 )
      {
        if ( start.iChar > 0 && tb.LineInfos[start.iLine].VisibleState == VisibleState.Visible )
        {
          if ( ( tb.AllowTabs )
          &&   ( start.iChar > 0 )
          &&   ( start.iChar - 1 < tb[start.iLine].Count )
          &&   ( tb[start.iLine][start.iChar - 1].c == '\t' ) )
          {
            int delta = start.iChar % tb.TabLength;
            if ( delta == 0 )
            {
              delta = tb.TabLength;
            }
            for ( int i = 0; i < delta; ++i )
            {
              if ( tb[start.iLine][start.iChar - 1].c == '\t' )
              {
                start.Offset( -1, 0 );
              }
              else
              {
                break;
              }
            }
          }
          else
          {
            start.Offset( -1, 0 );
          }
        }
        else
        {
          int i = tb.FindPrevVisibleLine( start.iLine );
          if ( i == start.iLine )
            return;
          start = new Place( tb[i].Count, i );
        }
      }

      if ( !shift )
        end = start;

      OnSelectionChanged();

      preferedPos = -1;
    }

    public void GoRight( bool shift )
    {
      ColumnSelectionMode = false;

      if ( !shift )
        if ( start < end )
        {
          Start = End;
          return;
        }

      if ( start.iLine < tb.LinesCount - 1 || start.iChar < tb[tb.LinesCount - 1].Count )
      {
        if ( start.iChar < tb[start.iLine].Count && tb.LineInfos[start.iLine].VisibleState == VisibleState.Visible )
        {
          if ( ( tb.AllowTabs )
          &&   ( tb[start.iLine][start.iChar].c == '\t' ) )
          {
            int delta = tb.TabLength - ( start.iChar % tb.TabLength );
            if ( delta == 0 )
            {
              delta = tb.TabLength;
            }
            start.Offset( delta, 0 );
          }
          else
          {
            start.Offset( 1, 0 );
          }
        }
        else
        {
          int i = tb.FindNextVisibleLine( start.iLine );
          if ( i == start.iLine )
            return;
          start = new Place( 0, i );
        }
      }

      if ( !shift )
        end = start;

      OnSelectionChanged();

      preferedPos = -1;
    }



    internal int AdjustXPosForTabs( int Line, int CharPos )
    {
      int delta = CharPos % tb.TabLength;
      if ( delta == 0 )
      {
        delta = tb.TabLength;
      }
      for ( int i = 0; i < delta; ++i )
      {
        if ( tb[Line][CharPos - 1].c == '\t' )
        {
          --CharPos;
        }
        else
        {
          break;
        }
      }
      return CharPos;
    }



    internal void GoUp( bool shift )
    {
      ColumnSelectionMode = false;

      if ( !shift )
        if ( start.iLine > end.iLine )
        {
          Start = End;
          return;
        }

      if ( preferedPos < 0 )
        preferedPos = start.iChar - tb.LineInfos[start.iLine].GetWordWrapStringStartPosition( tb.LineInfos[start.iLine].GetWordWrapStringIndex( start.iChar ) );

      int iWW = tb.LineInfos[start.iLine].GetWordWrapStringIndex( start.iChar );
      if ( iWW == 0 )
      {
        if ( start.iLine <= 0 )
        {
          if ( !shift )
          {
            end = start;
          }
          return;
        }
        int i = tb.FindPrevVisibleLine( start.iLine );
        if ( i == start.iLine )
          return;
        start.iLine = i;
        iWW = tb.LineInfos[start.iLine].WordWrapStringsCount;
      }

      if ( iWW > 0 )
      {
        int finish = tb.LineInfos[start.iLine].GetWordWrapStringFinishPosition( iWW - 1, tb[start.iLine] );
        start.iChar = tb.LineInfos[start.iLine].GetWordWrapStringStartPosition( iWW - 1 ) + preferedPos;
        if ( start.iChar > finish + 1 )
          start.iChar = finish + 1;
      }

      if ( ( tb.AllowTabs )
      &&   ( start.iChar < tb[start.iLine].Count )
      &&   ( ( start.iChar % tb.TabLength ) != 0 )
      &&   ( tb[start.iLine][start.iChar].c == '\t' ) )
      {
        start.iChar = AdjustXPosForTabs( start.iLine, start.iChar );
      }

      if ( !shift )
        end = start;

      OnSelectionChanged();
    }

    internal void GoPageUp( bool shift )
    {
      ColumnSelectionMode = false;

      if ( preferedPos < 0 )
        preferedPos = start.iChar - tb.LineInfos[start.iLine].GetWordWrapStringStartPosition( tb.LineInfos[start.iLine].GetWordWrapStringIndex( start.iChar ) );

      int pageHeight = ( tb.ClientRectangle.Height + tb.CharHeight - 1 ) / tb.CharHeight - 1;
      int scrollOffset = pageHeight * tb.CharHeight;

      for ( int i = 0; i < pageHeight; i++ )
      {
        int iWW = tb.LineInfos[start.iLine].GetWordWrapStringIndex( start.iChar );
        if ( iWW == 0 )
        {
          if ( start.iLine <= 0 )
            break;
          //pass hidden
          int newLine = tb.FindPrevVisibleLine( start.iLine );
          if ( newLine == start.iLine )
            break;
          start.iLine = newLine;
          iWW = tb.LineInfos[start.iLine].WordWrapStringsCount;
        }

        if ( iWW > 0 )
        {
          int finish = tb.LineInfos[start.iLine].GetWordWrapStringFinishPosition( iWW - 1, tb[start.iLine] );
          start.iChar = tb.LineInfos[start.iLine].GetWordWrapStringStartPosition( iWW - 1 ) + preferedPos;
          if ( start.iChar > finish + 1 )
            start.iChar = finish + 1;
        }
      }

      if ( ( tb.AllowTabs )
      &&   ( ( start.iChar % tb.TabLength ) != 0 )
      &&   ( start.iChar < tb[start.iLine].Count )
      &&   ( tb[start.iLine][start.iChar].c == '\t' ) )
      {
        start.iChar = AdjustXPosForTabs( start.iLine, start.iChar );
      }

      if ( !shift )
        end = start;

      if ( tb.VerticalScroll.Value < scrollOffset )
      {
        tb.VerticalScroll.Value = 0;
      }
      else
      {
        tb.VerticalScroll.Value -= scrollOffset;
      }

      OnSelectionChanged();
    }

    internal void GoDown( bool shift )
    {
      ColumnSelectionMode = false;

      if ( !shift )
        if ( start.iLine < end.iLine )
        {
          Start = End;
          return;
        }

      if ( preferedPos < 0 )
        preferedPos = start.iChar - tb.LineInfos[start.iLine].GetWordWrapStringStartPosition( tb.LineInfos[start.iLine].GetWordWrapStringIndex( start.iChar ) );

      int iWW = tb.LineInfos[start.iLine].GetWordWrapStringIndex( start.iChar );
      if ( iWW >= tb.LineInfos[start.iLine].WordWrapStringsCount - 1 )
      {
        if ( start.iLine >= tb.LinesCount - 1 )
        {
          if ( !shift )
            end = start;

          OnSelectionChanged();
          return;
        }
        //pass hidden
        int i = tb.FindNextVisibleLine( start.iLine );
        if ( i == start.iLine )
          return;
        start.iLine = i;
        iWW = -1;
      }

      if ( iWW < tb.LineInfos[start.iLine].WordWrapStringsCount - 1 )
      {
        int finish = tb.LineInfos[start.iLine].GetWordWrapStringFinishPosition( iWW + 1, tb[start.iLine] );
        start.iChar = tb.LineInfos[start.iLine].GetWordWrapStringStartPosition( iWW + 1 ) + preferedPos;
        if ( start.iChar > finish + 1 )
          start.iChar = finish + 1;
      }

      if ( ( tb.AllowTabs )
      &&   ( ( start.iChar % tb.TabLength ) != 0 )
      &&   ( start.iChar < tb[start.iLine].Count )
      &&   ( tb[start.iLine][start.iChar].c == '\t' ) )
      {
        start.iChar = AdjustXPosForTabs( start.iLine, start.iChar );
      }

      if ( !shift )
        end = start;

      OnSelectionChanged();
    }

    internal void GoPageDown( bool shift )
    {
      ColumnSelectionMode = false;

      if ( preferedPos < 0 )
        preferedPos = start.iChar - tb.LineInfos[start.iLine].GetWordWrapStringStartPosition( tb.LineInfos[start.iLine].GetWordWrapStringIndex( start.iChar ) );

      int pageHeight = ( tb.ClientRectangle.Height + tb.CharHeight - 1 ) / tb.CharHeight - 1;
      int scrollOffset = pageHeight * tb.CharHeight;

      for ( int i = 0; i < pageHeight; i++ )
      {
        int iWW = tb.LineInfos[start.iLine].GetWordWrapStringIndex( start.iChar );
        if ( iWW >= tb.LineInfos[start.iLine].WordWrapStringsCount - 1 )
        {
          if ( start.iLine >= tb.LinesCount - 1 )
            break;
          //pass hidden
          int newLine = tb.FindNextVisibleLine( start.iLine );
          if ( newLine == start.iLine )
            break;
          start.iLine = newLine;
          iWW = -1;
        }

        if ( iWW < tb.LineInfos[start.iLine].WordWrapStringsCount - 1 )
        {
          int finish = tb.LineInfos[start.iLine].GetWordWrapStringFinishPosition( iWW + 1, tb[start.iLine] );
          start.iChar = tb.LineInfos[start.iLine].GetWordWrapStringStartPosition( iWW + 1 ) + preferedPos;
          if ( start.iChar > finish + 1 )
            start.iChar = finish + 1;
        }
      }

      if ( ( tb.AllowTabs )
      &&   ( ( start.iChar % tb.TabLength ) != 0 )
      &&   ( start.iChar < tb[start.iLine].Count )
      &&   ( tb[start.iLine][start.iChar].c == '\t' ) )
      {
        start.iChar = AdjustXPosForTabs( start.iLine, start.iChar );
      }

      if ( !shift )
        end = start;

      if ( tb.VerticalScroll.Visible )
      {
        tb.VerticalScroll.Value += scrollOffset;
      }

      OnSelectionChanged();
    }

    internal void GoHome( bool shift )
    {
      ColumnSelectionMode = false;

      if ( start.iLine < 0 )
        return;

      if ( tb.LineInfos[start.iLine].VisibleState != VisibleState.Visible )
        return;

      start = new Place( 0, start.iLine );

      if ( !shift )
        end = start;

      OnSelectionChanged();

      preferedPos = -1;
    }

    internal void GoEnd( bool shift )
    {
      ColumnSelectionMode = false;

      if ( start.iLine < 0 )
        return;
      if ( tb.LineInfos[start.iLine].VisibleState != VisibleState.Visible )
        return;

      start = new Place( tb[start.iLine].Count, start.iLine );

      if ( !shift )
        end = start;

      OnSelectionChanged();

      preferedPos = -1;
    }

    /// <summary>
    /// Set style for range
    /// </summary>
    public void SetStyle( Style style )
    {
      //search code for style
      int code = tb.GetOrSetStyleLayerIndex( style );
      //set code to chars
      SetStyle( ToStyleIndex( code ) );
      //
      tb.Invalidate();
    }

    /// <summary>
    /// Set style for given regex pattern
    /// </summary>
    public void SetStyle( Style style, string regexPattern )
    {
      //search code for style
      StyleIndex layer = ToStyleIndex( tb.GetOrSetStyleLayerIndex( style ) );
      SetStyle( layer, regexPattern, RegexOptions.None );
    }

    /// <summary>
    /// Set style for given regex
    /// </summary>
    public void SetStyle( Style style, Regex regex )
    {
      //search code for style
      StyleIndex layer = ToStyleIndex( tb.GetOrSetStyleLayerIndex( style ) );
      SetStyle( layer, regex );
    }


    /// <summary>
    /// Set style for given regex pattern
    /// </summary>
    public void SetStyle( Style style, string regexPattern, RegexOptions options )
    {
      //search code for style
      StyleIndex layer = ToStyleIndex( tb.GetOrSetStyleLayerIndex( style ) );
      SetStyle( layer, regexPattern, options );
    }

    /// <summary>
    /// Set style for given regex pattern
    /// </summary>
    public void SetStyle( StyleIndex styleLayer, string regexPattern, RegexOptions options )
    {
      if ( Math.Abs( Start.iLine - End.iLine ) > 1000 )
        options |= SyntaxHighlighter.RegexCompiledOption;
      //
      foreach ( var range in GetRanges( regexPattern, options ) )
        range.SetStyle( styleLayer );
      //
      tb.Invalidate();
    }

    /// <summary>
    /// Set style for given regex pattern
    /// </summary>
    public void SetStyle( StyleIndex styleLayer, Regex regex )
    {
      foreach ( var range in GetRanges( regex ) )
        range.SetStyle( styleLayer );
      //
      tb.Invalidate();
    }

    /// <summary>
    /// Appends style to chars of range
    /// </summary>
    public void SetStyle( StyleIndex styleIndex )
    {
      //set code to chars
      int fromLine = Math.Min( End.iLine, Start.iLine );
      int toLine = Math.Max( End.iLine, Start.iLine );
      int fromChar = FromX;
      int toChar = ToX;
      if ( fromLine < 0 )
        return;
      //
      for ( int y = fromLine; y <= toLine; y++ )
      {
        int fromX = y == fromLine ? fromChar : 0;
        int toX = y == toLine ? Math.Min( toChar - 1, tb[y].Count - 1 ) : tb[y].Count - 1;
        for ( int x = fromX; x <= toX; x++ )
        {
          Char c = tb[y][x];
          c.style |= styleIndex;
          tb[y][x] = c;
        }
      }
    }

    /// <summary>
    /// Sets folding markers
    /// </summary>
    /// <param name="startFoldingPattern">Pattern for start folding line</param>
    /// <param name="finishFoldingPattern">Pattern for finish folding line</param>
    public void SetFoldingMarkers( string startFoldingPattern, string finishFoldingPattern )
    {
      SetFoldingMarkers( startFoldingPattern, finishFoldingPattern, SyntaxHighlighter.RegexCompiledOption );
    }

    /// <summary>
    /// Sets folding markers
    /// </summary>
    /// <param name="startFoldingPattern">Pattern for start folding line</param>
    /// <param name="finishFoldingPattern">Pattern for finish folding line</param>
    public void SetFoldingMarkers( string startFoldingPattern, string finishFoldingPattern, RegexOptions options )
    {
      if ( startFoldingPattern == finishFoldingPattern )
      {
        SetFoldingMarkers( startFoldingPattern, options );
        return;
      }

      foreach ( var range in GetRanges( startFoldingPattern, options ) )
        tb[range.Start.iLine].FoldingStartMarker = startFoldingPattern;

      foreach ( var range in GetRanges( finishFoldingPattern, options ) )
        tb[range.Start.iLine].FoldingEndMarker = startFoldingPattern;
      //
      tb.Invalidate();
    }

    /// <summary>
    /// Sets folding markers
    /// </summary>
    /// <param name="foldingPattern">Pattern for start and end folding line</param>
    public void SetFoldingMarkers( string foldingPattern, RegexOptions options )
    {
      foreach ( var range in GetRanges( foldingPattern, options ) )
      {
        if ( range.Start.iLine > 0 )
          tb[range.Start.iLine - 1].FoldingEndMarker = foldingPattern;
        tb[range.Start.iLine].FoldingStartMarker = foldingPattern;
      }

      tb.Invalidate();
    }
    /// <summary>
    /// Finds ranges for given regex pattern
    /// </summary>
    /// <param name="regexPattern">Regex pattern</param>
    /// <returns>Enumeration of ranges</returns>
    public IEnumerable<Range> GetRanges( string regexPattern )
    {
      return GetRanges( regexPattern, RegexOptions.None );
    }

    /// <summary>
    /// Finds ranges for given regex pattern
    /// </summary>
    /// <param name="regexPattern">Regex pattern</param>
    /// <returns>Enumeration of ranges</returns>
    public IEnumerable<Range> GetRanges( string regexPattern, RegexOptions options )
    {
      //get text
      string text;
      List<Place> charIndexToPlace;
      GetText( out text, out charIndexToPlace );
      //create regex
      Regex regex = new Regex( regexPattern, options );
      //
      foreach ( Match m in regex.Matches( text ) )
      {
        Range r = new Range( this.tb );
        //try get 'range' group, otherwise use group 0
        Group group = m.Groups["range"];
        if ( !group.Success )
          group = m.Groups[0];
        //
        r.Start = charIndexToPlace[group.Index];
        r.End = charIndexToPlace[group.Index + group.Length];
        yield return r;
      }
    }

    /// <summary>
    /// Finds ranges for given regex pattern.
    /// Search is separately in each line.
    /// This method requires less memory than GetRanges().
    /// </summary>
    /// <param name="regexPattern">Regex pattern</param>
    /// <returns>Enumeration of ranges</returns>
    public IEnumerable<Range> GetRangesByLines( string regexPattern, RegexOptions options )
    {
      var regex = new Regex( regexPattern, options );
      foreach ( var r in GetRangesByLines( regex ) )
        yield return r;
    }

    /// <summary>
    /// Finds ranges for given regex.
    /// Search is separately in each line.
    /// This method requires less memory than GetRanges().
    /// </summary>
    /// <param name="regex">Regex</param>
    /// <returns>Enumeration of ranges</returns>
    public IEnumerable<Range> GetRangesByLines( Regex regex )
    {
      Normalize();

      var fts = tb.TextSource as FileTextSource; //<----!!!! ugly

      //enumaerate lines
      for ( int iLine = Start.iLine; iLine <= End.iLine; iLine++ )
      {
        //
        bool isLineLoaded = fts != null ? fts.IsLineLoaded( iLine ) : true;
        //
        var r = new Range( tb, new Place( 0, iLine ), new Place( tb[iLine].Count, iLine ) );
        if ( iLine == Start.iLine || iLine == End.iLine )
          r = r.GetIntersectionWith( this );

        foreach ( var foundRange in r.GetRanges( regex ) )
          yield return foundRange;

        if ( !isLineLoaded )
          fts.UnloadLine( iLine );
      }
    }

    /// <summary>
    /// Finds ranges for given regex pattern.
    /// Search is separately in each line (order of lines is reversed).
    /// This method requires less memory than GetRanges().
    /// </summary>
    /// <param name="regexPattern">Regex pattern</param>
    /// <returns>Enumeration of ranges</returns>
    public IEnumerable<Range> GetRangesByLinesReversed( string regexPattern, RegexOptions options )
    {
      Normalize();
      //create regex
      Regex regex = new Regex( regexPattern, options );
      //
      var fts = tb.TextSource as FileTextSource; //<----!!!! ugly

      //enumaerate lines
      for ( int iLine = End.iLine; iLine >= Start.iLine; iLine-- )
      {
        //
        bool isLineLoaded = fts != null ? fts.IsLineLoaded( iLine ) : true;
        //
        var r = new Range( tb, new Place( 0, iLine ), new Place( tb[iLine].Count, iLine ) );
        if ( iLine == Start.iLine || iLine == End.iLine )
          r = r.GetIntersectionWith( this );

        var list = new List<Range>();

        foreach ( var foundRange in r.GetRanges( regex ) )
          list.Add( foundRange );

        for ( int i = list.Count - 1; i >= 0; i-- )
          yield return list[i];

        if ( !isLineLoaded )
          fts.UnloadLine( iLine );
      }
    }

    /// <summary>
    /// Finds ranges for given regex
    /// </summary>
    /// <returns>Enumeration of ranges</returns>
    public IEnumerable<Range> GetRanges( Regex regex )
    {
      //get text
      string text;
      List<Place> charIndexToPlace;
      GetText( out text, out charIndexToPlace );
      //
      foreach ( Match m in regex.Matches( text ) )
      {
        Range r = new Range( this.tb );
        //try get 'range' group, otherwise use group 0
        Group group = m.Groups["range"];
        if ( !group.Success )
          group = m.Groups[0];
        //
        r.Start = charIndexToPlace[group.Index];
        r.End = charIndexToPlace[group.Index + group.Length];
        yield return r;
      }
    }

    /// <summary>
    /// Clear styles of range
    /// </summary>
    public void ClearStyle( params Style[] styles )
    {
      try
      {
        ClearStyle( tb.GetStyleIndexMask( styles ) );
      }
      catch
      {
        ;
      }
    }

    /// <summary>
    /// Clear styles of range
    /// </summary>
    public void ClearStyle( StyleIndex styleIndex )
    {
      //set code to chars
      int fromLine = Math.Min( End.iLine, Start.iLine );
      int toLine = Math.Max( End.iLine, Start.iLine );
      int fromChar = FromX;
      int toChar = ToX;
      if ( fromLine < 0 )
        return;
      //
      for ( int y = fromLine; y <= toLine; y++ )
      {
        int fromX = y == fromLine ? fromChar : 0;
        int toX = y == toLine ? Math.Min( toChar - 1, tb[y].Count - 1 ) : tb[y].Count - 1;
        for ( int x = fromX; x <= toX; x++ )
        {
          Char c = tb[y][x];
          c.style &= ~styleIndex;
          tb[y][x] = c;
        }
      }
      //
      tb.Invalidate();
    }

    /// <summary>
    /// Clear folding markers of all lines of range
    /// </summary>
    public void ClearFoldingMarkers()
    {
      //set code to chars
      int fromLine = Math.Min( End.iLine, Start.iLine );
      int toLine = Math.Max( End.iLine, Start.iLine );
      if ( fromLine < 0 )
        return;
      //
      for ( int y = fromLine; y <= toLine; y++ )
        tb[y].ClearFoldingMarkers();
      //
      tb.Invalidate();
    }

    void OnSelectionChanged()
    {
      //clear cache
      cachedTextVersion = -1;
      cachedText = null;
      cachedCharIndexToPlace = null;
      //
      if ( tb.Selection == this )
        if ( updating == 0 )
          tb.OnSelectionChanged();
    }

    /// <summary>
    /// Starts selection position updating
    /// </summary>
    public void BeginUpdate()
    {
      updating++;
    }

    /// <summary>
    /// Ends selection position updating
    /// </summary>
    public void EndUpdate()
    {
      updating--;
      if ( updating == 0 )
        OnSelectionChanged();
    }

    public override string ToString()
    {
      return "Start: " + Start + " End: " + End;
    }

    /// <summary>
    /// Exchanges Start and End if End appears before Start
    /// </summary>
    public void Normalize()
    {
      if ( Start > End )
        Inverse();
    }

    /// <summary>
    /// Exchanges Start and End
    /// </summary>
    public void Inverse()
    {
      var temp = start;
      start = end;
      end = temp;
    }

    /// <summary>
    /// Expands range from first char of Start line to last char of End line
    /// </summary>
    public void Expand()
    {
      Normalize();
      start = new Place( 0, start.iLine );
      end = new Place( tb.GetLineLength( end.iLine ), end.iLine );
    }

    IEnumerator<Place> IEnumerable<Place>.GetEnumerator()
    {
      if ( ColumnSelectionMode )
      {
        foreach ( var p in GetEnumerator_ColumnSelectionMode() )
          yield return p;
        yield break;
      }

      int fromLine = Math.Min( end.iLine, start.iLine );
      int toLine = Math.Max( end.iLine, start.iLine );
      int fromChar = FromX;
      int toChar = ToX;
      if ( fromLine < 0 )
        yield break;
      //
      for ( int y = fromLine; y <= toLine; y++ )
      {
        int fromX = y == fromLine ? fromChar : 0;
        int toX = y == toLine ? Math.Min( toChar - 1, tb[y].Count - 1 ) : tb[y].Count - 1;
        for ( int x = fromX; x <= toX; x++ )
          yield return new Place( x, y );
      }
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
      return ( this as IEnumerable<Place> ).GetEnumerator();
    }

    /// <summary>
    /// Chars of range (exclude \n)
    /// </summary>
    public IEnumerable<Char> Chars
    {
      get
      {
        if ( ColumnSelectionMode )
        {
          foreach ( var p in GetEnumerator_ColumnSelectionMode() )
            yield return tb[p];
          yield break;
        }

        int fromLine = Math.Min( end.iLine, start.iLine );
        int toLine = Math.Max( end.iLine, start.iLine );
        int fromChar = FromX;
        int toChar = ToX;
        if ( fromLine < 0 )
          yield break;
        //
        for ( int y = fromLine; y <= toLine; y++ )
        {
          int fromX = y == fromLine ? fromChar : 0;
          int toX = y == toLine ? Math.Min( toChar - 1, tb[y].Count - 1 ) : tb[y].Count - 1;
          var line = tb[y];
          for ( int x = fromX; x <= toX; x++ )
            yield return line[x];
        }
      }
    }

    /// <summary>
    /// Get fragment of text around Start place. Returns maximal matched to pattern fragment.
    /// </summary>
    /// <param name="allowedSymbolsPattern">Allowed chars pattern for fragment</param>
    /// <returns>Range of found fragment</returns>
    public Range GetFragment( string allowedSymbolsPattern )
    {
      return GetFragment( allowedSymbolsPattern, RegexOptions.None );
    }

    /// <summary>
    /// Get fragment of text around Start place. Returns maximal matched to given Style.
    /// </summary>
    /// <param name="style">Allowed style for fragment</param>
    /// <returns>Range of found fragment</returns>
    public Range GetFragment( Style style, bool allowLineBreaks )
    {
      var mask = tb.GetStyleIndexMask( new Style[] { style } );
      //
      Range r = new Range( tb );
      r.Start = Start;
      //go left, check style
      while ( r.GoLeftThroughFolded() )
      {
        if ( !allowLineBreaks && r.CharAfterStart == '\n' )
          break;
        if ( r.Start.iChar < tb.GetLineLength( r.Start.iLine ) )
          if ( ( tb[r.Start].style & mask ) == 0 )
          {
            r.GoRightThroughFolded();
            break;
          }
      }
      Place startFragment = r.Start;

      r.Start = Start;
      //go right, check style
      do
      {
        if ( !allowLineBreaks && r.CharAfterStart == '\n' )
          break;
        if ( r.Start.iChar < tb.GetLineLength( r.Start.iLine ) )
          if ( ( tb[r.Start].style & mask ) == 0 )
            break;
      } while ( r.GoRightThroughFolded() );
      Place endFragment = r.Start;

      return new Range( tb, startFragment, endFragment );
    }

    /// <summary>
    /// Get fragment of text around Start place. Returns maximal mathed to pattern fragment.
    /// </summary>
    /// <param name="allowedSymbolsPattern">Allowed chars pattern for fragment</param>
    /// <returns>Range of found fragment</returns>
    public Range GetFragment( string allowedSymbolsPattern, RegexOptions options )
    {
      Range r = new Range( tb );
      r.Start = Start;
      Regex regex = new Regex( allowedSymbolsPattern, options );
      //go left, check symbols
      while ( r.GoLeftThroughFolded() )
      {
        if ( !regex.IsMatch( r.CharAfterStart.ToString() ) )
        {
          r.GoRightThroughFolded();
          break;
        }
      }
      Place startFragment = r.Start;

      r.Start = Start;
      //go right, check symbols
      do
      {
        if ( !regex.IsMatch( r.CharAfterStart.ToString() ) )
          break;
      } while ( r.GoRightThroughFolded() );
      Place endFragment = r.Start;

      return new Range( tb, startFragment, endFragment );
    }

    bool IsIdentifierChar( char c )
    {
      return char.IsLetterOrDigit( c ) || c == '_';
    }

    bool IsSpaceChar( char c )
    {
      return c == ' ' || c == '\t';
    }

    public void GoWordLeft( bool shift )
    {
      ColumnSelectionMode = false;

      if ( !shift && start > end )
      {
        Start = End;
        return;
      }

      Range range = this.Clone();//to OnSelectionChanged disable
      bool wasSpace = false;
      while ( IsSpaceChar( range.CharBeforeStart ) )
      {
        wasSpace = true;
        range.GoLeft( shift );
      }
      bool wasIdentifier = false;
      while ( IsIdentifierChar( range.CharBeforeStart ) )
      {
        wasIdentifier = true;
        range.GoLeft( shift );
      }
      if ( !wasIdentifier && ( !wasSpace || range.CharBeforeStart != '\n' ) )
        range.GoLeft( shift );
      this.Start = range.Start;
      this.End = range.End;

      if ( tb.LineInfos[Start.iLine].VisibleState != VisibleState.Visible )
        GoRight( shift );
    }

    public void GoWordRight( bool shift )
    {
      GoWordRight( shift, false );
    }

    public void GoWordRight( bool shift, bool goToStartOfNextWord )
    {
      ColumnSelectionMode = false;

      if ( !shift && start < end )
      {
        Start = End;
        return;
      }

      Range range = this.Clone();//to OnSelectionChanged disable

      bool wasNewLine = false;


      if ( range.CharAfterStart == '\n' )
      {
        range.GoRight( shift );
        wasNewLine = true;
      }

      bool wasSpace = false;
      while ( IsSpaceChar( range.CharAfterStart ) )
      {
        wasSpace = true;
        range.GoRight( shift );
      }

      if ( !( ( wasSpace || wasNewLine ) && goToStartOfNextWord ) )
      {

        bool wasIdentifier = false;
        while ( IsIdentifierChar( range.CharAfterStart ) )
        {
          wasIdentifier = true;
          range.GoRight( shift );
        }

        if ( !wasIdentifier )
          range.GoRight( shift );

        if ( goToStartOfNextWord && !wasSpace )
          while ( IsSpaceChar( range.CharAfterStart ) )
            range.GoRight( shift );
      }

      this.Start = range.Start;
      this.End = range.End;

      if ( tb.LineInfos[Start.iLine].VisibleState != VisibleState.Visible )
        GoLeft( shift );
    }

    internal void GoFirst( bool shift )
    {
      ColumnSelectionMode = false;

      start = new Place( 0, 0 );
      if ( tb.LineInfos[Start.iLine].VisibleState != VisibleState.Visible )
        tb.ExpandBlock( Start.iLine );

      if ( !shift )
        end = start;

      OnSelectionChanged();
    }

    internal void GoLast( bool shift )
    {
      ColumnSelectionMode = false;

      start = new Place( tb[tb.LinesCount - 1].Count, tb.LinesCount - 1 );
      if ( tb.LineInfos[Start.iLine].VisibleState != VisibleState.Visible )
        tb.ExpandBlock( Start.iLine );

      if ( !shift )
        end = start;

      OnSelectionChanged();
    }

    public static StyleIndex ToStyleIndex( int i )
    {
      return (StyleIndex)( 1 << i );
    }

    public RangeRect Bounds
    {
      get
      {
        int minX = Math.Min( Start.iChar, End.iChar );
        int minY = Math.Min( Start.iLine, End.iLine );
        int maxX = Math.Max( Start.iChar, End.iChar );
        int maxY = Math.Max( Start.iLine, End.iLine );
        return new RangeRect( minY, minX, maxY, maxX );
      }
    }

    public IEnumerable<Range> GetSubRanges( bool includeEmpty )
    {
      if ( !ColumnSelectionMode )
      {
        yield return this;
        yield break;
      }

      var rect = Bounds;
      for ( int y = rect.iStartLine; y <= rect.iEndLine; y++ )
      {
        if ( rect.iStartChar > tb[y].Count && !includeEmpty )
          continue;

        var r = new Range( tb, rect.iStartChar, y, Math.Min( rect.iEndChar, tb[y].Count ), y );
        yield return r;
      }
    }

    /// <summary>
    /// Range is readonly?
    /// This property return True if any char of the range contains ReadOnlyStyle.
    /// Set this property to True/False to mark chars of the range as Readonly/Writable.
    /// </summary>
    public bool ReadOnly
    {
      get
      {
        if ( tb.ReadOnly )
          return true;

        ReadOnlyStyle readonlyStyle = null;
        foreach ( var style in tb.Styles )
          if ( style is ReadOnlyStyle )
          {
            readonlyStyle = (ReadOnlyStyle)style;
            break;
          }

        if ( readonlyStyle != null )
        {
          var si = ToStyleIndex( tb.GetStyleIndex( readonlyStyle ) );

          if ( IsEmpty )
          {
            //check previous and next chars
            var line = tb[start.iLine];
            if ( columnSelectionMode )
            {
              foreach ( var sr in GetSubRanges( false ) )
              {
                line = tb[sr.start.iLine];
                if ( sr.start.iChar < line.Count && sr.start.iChar > 0 )
                {
                  var left = line[sr.start.iChar - 1];
                  var right = line[sr.start.iChar];
                  if ( ( left.style & si ) != 0 &&
                      ( right.style & si ) != 0 )
                    return true;//we are between readonly chars
                }
              }
            }
            else
              if ( start.iChar < line.Count && start.iChar > 0 )
              {
                var left = line[start.iChar - 1];
                var right = line[start.iChar];
                if ( ( left.style & si ) != 0 &&
                    ( right.style & si ) != 0 )
                  return true;//we are between readonly chars
              }
          }
          else
            foreach ( Char c in Chars )
              if ( ( c.style & si ) != 0 )//found char with ReadonlyStyle
                return true;
        }

        return false;
      }

      set
      {
        //find exists ReadOnlyStyle of style buffer
        ReadOnlyStyle readonlyStyle = null;
        foreach ( var style in tb.Styles )
          if ( style is ReadOnlyStyle )
          {
            readonlyStyle = (ReadOnlyStyle)style;
            break;
          }

        //create ReadOnlyStyle
        if ( readonlyStyle == null )
          readonlyStyle = new ReadOnlyStyle();

        //set/clear style
        if ( value )
          SetStyle( readonlyStyle );
        else
          ClearStyle( readonlyStyle );
      }
    }

    /// <summary>
    /// Is char before range readonly
    /// </summary>
    /// <returns></returns>
    public bool IsReadOnlyLeftChar()
    {
      if ( tb.ReadOnly )
        return true;

      var r = Clone();

      r.Normalize();
      if ( r.start.iChar == 0 )
        return false;
      if ( ColumnSelectionMode )
        r.GoLeft_ColumnSelectionMode();
      else
        r.GoLeft( true );

      return r.ReadOnly;
    }

    /// <summary>
    /// Is char after range readonly
    /// </summary>
    /// <returns></returns>
    public bool IsReadOnlyRightChar()
    {
      if ( tb.ReadOnly )
        return true;

      var r = Clone();

      r.Normalize();
      if ( r.end.iChar >= tb[end.iLine].Count )
        return false;
      if ( ColumnSelectionMode )
        r.GoRight_ColumnSelectionMode();
      else
        r.GoRight( true );

      return r.ReadOnly;
    }

    public IEnumerable<Place> GetPlacesCyclic( Place startPlace )
    {
      return GetPlacesCyclic( startPlace, false );
    }

    public IEnumerable<Place> GetPlacesCyclic( Place startPlace, bool backward )
    {
      if ( backward )
      {
        var r = new Range( this.tb, startPlace, startPlace );
        while ( r.GoLeft() && r.start >= Start )
        {
          if ( r.Start.iChar < tb[r.Start.iLine].Count )
            yield return r.Start;
        }

        r = new Range( this.tb, End, End );
        while ( r.GoLeft() && r.start >= startPlace )
        {
          if ( r.Start.iChar < tb[r.Start.iLine].Count )
            yield return r.Start;
        }
      }
      else
      {
        var r = new Range( this.tb, startPlace, startPlace );
        if ( startPlace < End )
          do
          {
            if ( r.Start.iChar < tb[r.Start.iLine].Count )
              yield return r.Start;
          } while ( r.GoRight() );

        r = new Range( this.tb, Start, Start );
        if ( r.Start < startPlace )
          do
          {
            if ( r.Start.iChar < tb[r.Start.iLine].Count )
              yield return r.Start;
          } while ( r.GoRight() && r.Start < startPlace );
      }
    }

    #region ColumnSelectionMode

    private Range GetIntersectionWith_ColumnSelectionMode( Range range )
    {
      if ( range.Start.iLine != range.End.iLine )
        return new Range( tb, Start, Start );
      var rect = Bounds;
      if ( range.Start.iLine < rect.iStartLine || range.Start.iLine > rect.iEndLine )
        return new Range( tb, Start, Start );

      return new Range( tb, rect.iStartChar, range.Start.iLine, rect.iEndChar, range.Start.iLine ).GetIntersectionWith( range );
    }

    private bool GoRightThroughFolded_ColumnSelectionMode()
    {
      var boundes = Bounds;
      var endOfLines = true;
      for ( int iLine = boundes.iStartLine; iLine <= boundes.iEndLine; iLine++ )
        if ( boundes.iEndChar < tb[iLine].Count )
        {
          endOfLines = false;
          break;
        }

      if ( endOfLines )
        return false;

      var start = Start;
      var end = End;
      start.Offset( 1, 0 );
      end.Offset( 1, 0 );
      BeginUpdate();
      Start = start;
      End = end;
      EndUpdate();

      return true;
    }

    private IEnumerable<Place> GetEnumerator_ColumnSelectionMode()
    {
      var bounds = Bounds;
      if ( bounds.iStartLine < 0 )
        yield break;
      //
      for ( int y = bounds.iStartLine; y <= bounds.iEndLine; y++ )
      {
        for ( int x = bounds.iStartChar; x < bounds.iEndChar; x++ )
        {
          if ( x < tb[y].Count )
            yield return new Place( x, y );
        }
      }
    }

    private string Text_ColumnSelectionMode
    {
      get
      {
        StringBuilder sb = new StringBuilder();
        var bounds = Bounds;
        if ( bounds.iStartLine < 0 )
          return "";
        //
        for ( int y = bounds.iStartLine; y <= bounds.iEndLine; y++ )
        {
          for ( int x = bounds.iStartChar; x < bounds.iEndChar; x++ )
          {
            if ( x < tb[y].Count )
              sb.Append( tb[y][x].c );
          }
          if ( bounds.iEndLine != bounds.iStartLine && y != bounds.iEndLine )
            sb.AppendLine();
        }

        return sb.ToString();
      }
    }

    internal void GoDown_ColumnSelectionMode()
    {
      var iLine = tb.FindNextVisibleLine( End.iLine );
      End = new Place( End.iChar, iLine );
    }

    internal void GoUp_ColumnSelectionMode()
    {
      var iLine = tb.FindPrevVisibleLine( End.iLine );
      End = new Place( End.iChar, iLine );
    }

    internal void GoRight_ColumnSelectionMode()
    {
      End = new Place( End.iChar + 1, End.iLine );
    }

    internal void GoLeft_ColumnSelectionMode()
    {
      if ( End.iChar > 0 )
        End = new Place( End.iChar - 1, End.iLine );
    }

    #endregion
  }

  public struct RangeRect
  {
    public RangeRect( int iStartLine, int iStartChar, int iEndLine, int iEndChar )
    {
      this.iStartLine = iStartLine;
      this.iStartChar = iStartChar;
      this.iEndLine = iEndLine;
      this.iEndChar = iEndChar;
    }

    public int iStartLine;
    public int iStartChar;
    public int iEndLine;
    public int iEndChar;
  }
}
